using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using Castle.Core;
using Castle.Core.Logging;
using Castle.Core.Configuration;
using Castle.Core.Interceptor;
using Castle.MicroKernel.Facilities;

namespace Castle.Facilities.PolicyInjection
{
    public class Handler
    {
        private Type _targetType;

        public Type TargetType
        {
            get
            {
                return _targetType;
            }
        }

        private string _preCallMethodName = string.Empty;

        public string PreCallMethodName
        {
            get
            {
                return _preCallMethodName;
            }            
        }

        private string _postCallMethodName = string.Empty;

        public string PostCallMethodName
        {
            get
            {
                return _postCallMethodName;
            }
        }

        private string _onExceptionMethodName = string.Empty;

        public string OnExceptionMethodName
        {
            get
            {
                return _onExceptionMethodName;
            }
        }

        private HandlerOnExceptionResult _onExceptionResult = HandlerOnExceptionResult.Rethrow;

        public HandlerOnExceptionResult OnExceptionResult
        {
            get
            {
                return _onExceptionResult;
            }            
        }
				

        public Handler(IConfiguration configuration)
        {
            InspectTagElement(configuration);
            AddHandlerMethods(configuration);
            AddHandlerInstance();
        }

        private void InspectTagElement(IConfiguration configuration)
        {
            if (configuration.Name != "handler")
            {
                throw new InvalidOperationException("Policy handler element must be <handler>");
            }
            if (string.IsNullOrEmpty(configuration.Attributes["type"]) == true)
            {
                throw new InvalidOperationException("Policy handler element must have a \"type\" attribute");
            }
            string targetType = configuration.Attributes["type"];
            _targetType = Type.GetType(targetType, false);
            if (_targetType == null)
            {
                throw new InvalidOperationException("Policy handler type " + targetType + " was not found");
            }
            if ((configuration.Children.Count < 1) || (configuration.Children.Count > 3))
            {
                throw new InvalidOperationException("Policy handler element must have at least one child and at most " +
                    "three");
            }
        }

        private void AddHandlerMethods(IConfiguration configuration)
        {
            for (int i = 0; i < configuration.Children.Count; i++)
            {
                IConfiguration methodConfiguration = configuration.Children[i];
                switch (methodConfiguration.Name)
                {
                    case "precall":
                        if (string.IsNullOrEmpty(_preCallMethodName) == true)
                        {
                            _preCallMethodName = InspectHandlerMethod(_targetType, i, methodConfiguration);
                        }
                        else
                        {
                            throw new InvalidOperationException("Policy handler already has a precall method");
                        }
                        break;
                    case "postcall":
                        if (string.IsNullOrEmpty(_postCallMethodName) == true)
                        {
                            _postCallMethodName = InspectHandlerMethod(_targetType, i, methodConfiguration);
                        }
                        else
                        {
                            throw new InvalidOperationException("Policy handler already has a postcall method");
                        }
                        break;
                    case "onexception":
                        if (string.IsNullOrEmpty(_onExceptionMethodName) == true)
                        {
                            _onExceptionMethodName = InspectHandlerMethod(_targetType, i, methodConfiguration);
                            _onExceptionResult = InspectOnExceptionResult(_targetType, i, methodConfiguration);
                        }
                        else
                        {
                            throw new InvalidOperationException("Policy handler already has a onexcpetion method");
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        private HandlerOnExceptionResult InspectOnExceptionResult(Type type, int i, IConfiguration methodConfiguration)
        {
            if (string.IsNullOrEmpty(methodConfiguration.Attributes["result"]) == true)
            {
                throw new InvalidOperationException("Policy handler onexception element must have a \"result\" attribute");
            }
            try
            {
                HandlerOnExceptionResult result = (HandlerOnExceptionResult)Enum.Parse(typeof(HandlerOnExceptionResult),
                    methodConfiguration.Attributes["result"]);
                return result;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Invalid policy handler onexception result \"" + 
                    methodConfiguration.Attributes["result"] + "\"", ex);
            }
        }

        private string InspectHandlerMethod(Type type, int i, IConfiguration methodConfiguration)
        {            
            if (string.IsNullOrEmpty(methodConfiguration.Attributes["method"]) == true)
            {
                throw new InvalidOperationException("Policy handler method element must have a \"method\" attribute");
            }
            string methodName = methodConfiguration.Attributes["method"];
            try
            {
                MethodInfo methodInfo = type.GetMethod(methodName);
                int parametersCount = 1;
                switch (methodConfiguration.Name)
                {
                    case "precall":
                        if (methodInfo.ReturnType != typeof(HandlerPrecallResult))
                        {
                            throw new InvalidOperationException("Policy handler precall method " + methodName +
                                " must return " + typeof(HandlerPrecallResult).FullName + " in handler type " +
                                type.FullName);
                        }
                        break;
                    case "postcall":
                        if (methodInfo.ReturnType != typeof(void))
                        {
                            throw new InvalidOperationException("Policy handler precall method " + methodName +
                                " must return " + typeof(void).FullName + " in handler type " + type.FullName);
                        }
                        break;
                    case "onexception":
                        if (methodInfo.ReturnType != typeof(void))
                        {
                            throw new InvalidOperationException("Policy handler onexception method " + methodName +
                                " must return " + typeof(void).FullName + " in handler type " + type.FullName);
                        }
                        parametersCount = 2;
                        break;
                }                
                ParameterInfo[] parameterCollection = methodInfo.GetParameters();
                if (parameterCollection.Length != parametersCount)
                {
                    throw new InvalidOperationException("Policy handler method " + methodName + " must have " +
                        parametersCount.ToString() + " arguments in handler type " + type.FullName);
                }
                if (parameterCollection[0].ParameterType != typeof(HandlerContext))
                {
                    throw new InvalidOperationException("Policy handler method " + methodName + " must have a " +
                        typeof(HandlerContext).FullName + " argument in handler type " + type.FullName);
                }
                if (parametersCount == 2)
                {
                    if (parameterCollection[1].ParameterType != typeof(Exception))
                    {
                        throw new InvalidOperationException("Policy handler onexception method " + methodName + " must have a " +
                            typeof(Exception).FullName + " argument in handler type " + type.FullName);
                    }
                }
                return methodInfo.Name;
            }
            catch (InvalidOperationException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Policy handler method " + methodName + " was not " +
                    "found in handler type " + type.FullName, ex);
            }
        }

        private void AddHandlerInstance()
        {
            if (PolicyFacility.HandlerInstanceCollection.ContainsKey(_targetType) == false)
            {
                try
                {
                    object handler = Activator.CreateInstance(_targetType);
                    if (handler == null)
                    {
                        throw new InvalidOperationException("Policy handler " + _targetType.FullName + " null " +
                            "intantiation");
                    }                    
                    PolicyFacility.HandlerInstanceCollection.Add(_targetType, handler);
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException("Cannot instantiate correctly policy handler " +
                        _targetType.FullName, ex);
                }
            }
        }
    }
}
